package top.zy.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import io.searchbox.client.JestClient;
import io.searchbox.core.*;
import io.searchbox.core.search.aggregation.MetricAggregation;
import io.searchbox.core.search.aggregation.TermsAggregation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.translog.Translog;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import top.zy.common.zymallcommon.utils.exception.ZymalException;
import top.zy.common.zymallcommon.utils.util.JsonUtils;
import top.zy.common.zymallcommon.vo.PageResult;
import top.zy.search.client.BrandClient;
import top.zy.search.client.CategoryClient;
import top.zy.search.client.GoodsClient;
import top.zy.search.client.SpecClient;
import top.zy.search.dto.Goods;
import top.zy.search.dto.SearchRequest;
import top.zy.search.dto.SearchSkuVo;
import top.zy.search.vo.SearchVo;
import top.zy.service.zymallgoodsinterface.entity.*;
import top.zy.service.zymallgoodsinterface.vo.GoodsVo;
import top.zy.service.zymallgoodsinterface.vo.SkuVo;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: HouZeYu
 * @Description:
 * @Date: Created in 16:22 2019/7/2
 */
@Service
@Slf4j
public class SearchServiceImpl implements SearchService {
    private static final String CATEGORY_AGG_NAME = "category_agg";
    private static final String BRAND_AGG_NAME = "brand_agg";
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecClient specClient;

    @Autowired
    private JestClient jestClient;


    /***
     * 构建导入es的数据类型
     * @param spu
     * @return
     */
    @Override
    public Goods buildGoods(Spu spu) {
        //查询分类
        List<Category> categoryList = categoryClient.queryCategoryListByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        if (CollectionUtils.isEmpty(categoryList)){
            throw new ZymalException("");
        }
        List<String> names = categoryList.stream().map(Category::getName).collect(Collectors.toList());
       //查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());
         if (brand==null){
            throw new ZymalException("");
         }
        //搜索字段
        String all=spu.getSubTitle()+ StringUtils.join(names," "+brand.getName());

        List<SkuVo> skuVoList = goodsClient.querySkuBySpuId(spu.getId());
        if (CollectionUtils.isEmpty(skuVoList)){
            throw new ZymalException("");
        }
        //处理sku
        List<SearchSkuVo> skus=new ArrayList<>();
        //价格集合
        Set<Long> priceSet=new TreeSet<>();
        for (SkuVo skuVo : skuVoList) {
            SearchSkuVo searchSkuVo=new SearchSkuVo();
            BeanUtils.copyProperties(skuVo,searchSkuVo);
            searchSkuVo.setImages(StringUtils.substringBefore(skuVo.getImages(),","));
            skus.add(searchSkuVo);
            //处理价格
            priceSet.add(skuVo.getPrice());
        }
        //查询规格参数
        List<SpecParam> params=specClient.queryParams(null,spu.getCid3(),true);
        //查询商品详情
         SpuDetail spuDetail=goodsClient.querySpuDetailById(spu.getId());
         //获取通用规格参数
        Map<String, String> genericSpec = JsonUtils.parseMap(spuDetail.getGenericSpec(), String.class, String.class);
        //获取特有规格参数
        Map<String, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<String, List<String>>>() {});

        //规格参数集合 key是规格参数的名称 value是规格参数的值
         Map<String, Object> specs=new HashMap<>();
        for (SpecParam param : params) {
            //规格名称
            String paramKey=param.getName();
            //规格值
            Object paramValue;
            if (param.getGeneric()){
                 paramValue=genericSpec.get(param.getId().toString());
                 //判断是否是数值类型
                if (param.getNumeric()){
                    //处理数值段
                   paramValue=chooseSegment(paramValue.toString(),param);
                }
            }else {
                 paramValue=specialSpec.get(param.getId().toString());
            }
                specs.put(paramKey,paramValue);
        }
         Goods goods=new Goods();
         goods.setBrandId(spu.getBrandId());
         goods.setCid1(spu.getCid1());
         goods.setCid2(spu.getCid2());
         goods.setCid3(spu.getCid3());
         goods.setCreateTime(new Date());
         goods.setId(spu.getId());
         goods.setAll(all); //搜索字段，包含标题，分类，品牌，规格
         goods.setPrice(priceSet); // 所有sku价格集合
         goods.setSkus(JsonUtils.toString(skus)); // 所有sku的集合的json格式
         goods.setSpecs(specs); // 所有可搜索的规格参数
         goods.setSubTitle(spu.getSubTitle());
        return goods;
    }

    /***
     * 将商品规格参数分段处理
     * @param paramValue
     * @param param
     * @return
     */
    private Object chooseSegment(String paramValue, SpecParam param) {
        //将数值类型转换为double
         double value= NumberUtils.toDouble(paramValue);
         String result="其他";
        for (String segment : param.getSegments().split(",")) {
            //获取数值范围
            String[] segs = segment.split("-");
            double  begin=NumberUtils.toDouble(segs[0]);
            double end=Double.MAX_VALUE;
            if (segs.length==2){
                 end=NumberUtils.toDouble(segs[1]);
            }
            //判断是否在范围内
            if (value>=begin&&value<end){
               if (segs.length==1){
                   result=segs[0]+param.getUnit()+"以上";
               }else if (begin==0){
                   result=segs[1]+param.getUnit()+"以下";
               }else {
                   result=segment+param.getUnit();
               }
               break;
            }

        }
         return result;
    }

    /***
     * 导入数据到es
     * @param goods
     */
    @Override
    public void saveGoodsTOEs(Goods goods) {
        Bulk.Builder bulk = new Bulk.Builder();
        Index index = new Index.Builder(goods).index("good").type("docs").id(goods.getId().toString()).build();
        bulk.addAction(index);
        try {
            jestClient.execute(bulk.build());
            log.info("导入索引indexName:{},goodsId:{},数据成功","goods",goods.getId());
        } catch (IOException e) {
           log.error("indexName: {},goodsId:{},导入ES数据出错","goods",goods.getId());
        }
    }

    /***
     * 搜索
     * @param searchRequest
     * @return
     */
    @Override
    public SearchVo searchPage(SearchRequest searchRequest) {
        Integer page=searchRequest.getPage();
        Integer size=searchRequest.getSize();
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //基本查询条件
      QueryBuilder basicQuery=buildBasicQuery(QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("all", searchRequest.getKey())),searchRequest.getFilter());
        sourceBuilder.query(basicQuery)
                .fetchSource(new String[]{"id","skus","subTitle"},null)
                .from(page)
                .size(size);
        TermsAggregationBuilder categoryAgg=AggregationBuilders.terms(CATEGORY_AGG_NAME).field("cid3");
        TermsAggregationBuilder brandAgg =AggregationBuilders.terms(BRAND_AGG_NAME).field("brandId");
        sourceBuilder.aggregation(categoryAgg).aggregation(brandAgg);
        Search search=new Search.Builder(sourceBuilder.toString()).addIndex("goods").build();
        try {
            SearchVo searchVo=new SearchVo();
            SearchResult searchResult=jestClient.execute(search);
            List<Goods> goodsList=new ArrayList<>();
            searchResult.getHits(Goods.class).stream().forEach(param->{
              Goods goods=param.source;
              goodsList.add(goods);
            });
            Long totalPage;
            if ((searchResult.getTotal()%searchRequest.getSize())==0){
                totalPage=searchResult.getTotal()/searchRequest.getSize();
            }else {
                totalPage=(searchResult.getTotal()/searchRequest.getSize())+1;
            }
            //解析聚合结果
            MetricAggregation aggregations = searchResult.getAggregations();
            List<Category> categoryList=parseCategoryAgg(aggregations.getTermsAggregation(CATEGORY_AGG_NAME));
            List<Brand> brandList=parseBrandAgg(aggregations.getTermsAggregation(BRAND_AGG_NAME));
            //完成规格参数聚合
            List<Map<String,Object>> specs=null;
            if (!CollectionUtils.isEmpty(categoryList)&&categoryList.size()==1){
              //商品分类存在并且数量为一可以聚合规格参数
                specs=buildSpecificationAgg(categoryList.get(0).getId(),basicQuery);
            }
            searchVo.setBrands(brandList);
            searchVo.setCategories(categoryList);
            searchVo.setTotalPage(totalPage.intValue());
            searchVo.setTotal(searchResult.getTotal());
            searchVo.setItems(goodsList);
            searchVo.setSpecs(specs);
            return searchVo;
        } catch (IOException e) {
            return null;
        }

    }

    /**
     * 构建过滤查询条件
     * @param must
     * @param filter
     * @return
     */
    private QueryBuilder buildBasicQuery(BoolQueryBuilder must, Map<String, String> filter) {
        for (Map.Entry<String, String> entry : filter.entrySet()) {
          String specName=entry.getKey();
          String specValue=entry.getValue();
          if (!"cid3".equals(specName)&&!"brandId".equals(specName)){
              specName="specs."+specName+".keyword";
          }
          must.filter(QueryBuilders.termQuery(specName,specValue));
        }
        return must;
    }

    /**
     * 根据基本基本条件聚合规格参数
     * @param cid
     * @param basicsQuery
     * @return
     */
    private List<Map<String, Object>> buildSpecificationAgg(Long cid, QueryBuilder basicsQuery) {
        List<Map<String, Object>> specs=new ArrayList<>();
        //查询需要聚合的规格参数
        List<SpecParam> params = specClient.queryParams(null, cid, true);
        //聚合
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
         sourceBuilder.query(basicsQuery);
        for (SpecParam param : params) {
           sourceBuilder.aggregation(AggregationBuilders.terms(param.getName()).field("specs."+param.getName()+".keyword"));
        }
        Search search=new Search.Builder(sourceBuilder.toString()).addIndex("goods").build();
        try {
            SearchResult searchResult=jestClient.execute(search);
            MetricAggregation aggregations = searchResult.getAggregations();
            for (SpecParam param : params) {
                String name=param.getName();
                TermsAggregation termsAggregation = aggregations.getTermsAggregation(param.getName());
                List<String> options = termsAggregation.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList());
                Map<String,Object> map=new HashMap<>();
                map.put("k",name);
                map.put("options",options);
                specs.add(map);
            }
        } catch (IOException e) {
              log.error("规格参数聚合失败",e);
        }
        //获取结果并解析
        return specs;
    }

    /**
     * 解析品牌聚合
     * @param terms
     * @return
     */
    private List<Brand> parseBrandAgg(TermsAggregation terms) {
        List<Brand> brandList = null;
        try {
            List<Long> ids = terms.getBuckets().stream().map(b -> Long.valueOf(b.getKeyAsString())).collect(Collectors.toList());

            brandList = brandClient.queryBrandByIds(ids);
            return brandList;

        } catch (Exception e) {
            log.error("{搜索服务}查询品牌异常:",e);
            return null;
        }

    }

    /**
     * 解析分类聚合
     * @param terms
     * @return
     */
    private List<Category> parseCategoryAgg(TermsAggregation terms) {
        try {
            List<Long> ids = terms.getBuckets().stream().map(b -> Long.valueOf(b.getKeyAsString())).collect(Collectors.toList());
            List<Category> categoryList = categoryClient.queryCategoryListByIds(ids);
            return categoryList;
        } catch (Exception e) {
            log.error("{搜索服务}查询分类异常:",e);
            return null;
        }
    }

    /***
     * 创建或更新索引数据
     * @param spuId
     */
    @Override
    public void createAndUpdateIndex(Long spuId) {
        GoodsVo goodsVo = goodsClient.querySpuBySpuId(spuId);
        Goods goods = buildGoods(goodsVo);
        saveGoodsTOEs(goods);
    }


    /***
     * 删除索引数据
     * @param spuId
     */
    @Override
    public void deleteIndex(Long spuId) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().filter(QueryBuilders.termsQuery("id", spuId.toString()));
        DeleteByQuery deleteByQuery = new DeleteByQuery.Builder(new SearchSourceBuilder().query(boolQueryBuilder).toString()).build();
        try {
            jestClient.execute(deleteByQuery);
            log.info("删除索引indexName:{},goodsId:{},数据成功","goods",spuId);
        } catch (IOException e) {
            log.error("删除索引indexName: {},goodsId:{},导入ES数据出错","goods",spuId);
        }
    }
}
